package co.com.smart.eagle.client.view.impl.smartcelltable;

import static com.google.gwt.dom.client.BrowserEvents.BLUR;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;


import com.google.gwt.cell.client.AbstractEditableCell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.OptionElement;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.text.shared.SafeHtmlRenderer;
import com.google.gwt.text.shared.SimpleSafeHtmlRenderer;

/**
 * Implementacion de {@link ClickTableTextCell} para editar 
 * en valor en la celda. 
 *
 * An editable text cell. Click to edit, escape to cancel, return to commit.
 */
public class SmartEditSelectionCell extends AbstractEditableCell<String, SmartEditSelectionCell.ViewData> {
	
	private ValidarCambio validarCambio;
	private String 		  validador;	

	public interface ValidarCambio{
		boolean esValido();
	}
	
	interface Template extends SafeHtmlTemplates {
	    @Template("<option label=\"{1}\" value=\"{0}\">{1}</option>")
	    SafeHtml deselected(String value, String option);

	    @Template("<option label=\"{1}\" value=\"{0}\" selected=\"selected\">{1}</option>")
	    SafeHtml selected(String value, String option);
	  }
  
  private boolean isEditable;
	
	  
	/**
	 * @return the isEditable
	 */
	public boolean isEditable() {
		return isEditable;
	}
	
	
	/**
	 * @param isEditable the isEditable to set
	 */
	public void setEditable(boolean isEditable) {
		this.isEditable = isEditable;
	}
	
/**
   * The view data object used by this cell. We need to store both the text and
   * the state because this cell is rendered differently in edit mode. If we did
   * not store the edit state, refreshing the cell with view data would always
   * put us in to edit state, rendering a text box instead of the new text
   * string.
   */
  static class ViewData {

    private boolean isEditing;

    /**
     * If true, this is not the first edit.
     */
    private boolean isEditingAgain;

    /**
     * Keep track of the original value at the start of the edit, which might be
     * the edited value from the previous edit and NOT the actual value.
     */
    private String original;

    private String text;
    
    /**
     * Construct a new ViewData in editing mode.
     *
     * @param text the text to edit
     */
    public ViewData(String text) {
      this.original = text;
      this.text = text;
      this.isEditing = true;
      this.isEditingAgain = false;
    }

    @Override
    public boolean equals(Object o) {
      if (o == null) {
        return false;
      }
      ViewData vd = (ViewData) o;
      return equalsOrBothNull(original, vd.original)
          && equalsOrBothNull(text, vd.text) && isEditing == vd.isEditing
          && isEditingAgain == vd.isEditingAgain;
    }

    public String getOriginal() {
      return original;
    }

    public String getText() {
      return text;
    }

    @Override
    public int hashCode() {
      return original.hashCode() + text.hashCode()
          + Boolean.valueOf(isEditing).hashCode() * 29
          + Boolean.valueOf(isEditingAgain).hashCode();
    }

    public boolean isEditing() {
      return isEditing;
    }

    public boolean isEditingAgain() {
      return isEditingAgain;
    }

    public void setEditing(boolean isEditing) {
      boolean wasEditing = this.isEditing;
      this.isEditing = isEditing;

      // This is a subsequent edit, so start from where we left off.
      if (!wasEditing && isEditing) {
        isEditingAgain = true;
        original = text;
      }
    }

    public void setText(String text) {
      this.text = text;
    }

    private boolean equalsOrBothNull(Object o1, Object o2) {
      return (o1 == null) ? o2 == null : o1.equals(o2);
    }
  }

  private static Template template;
  
  

private final SafeHtmlRenderer<String> renderer;

  /**
   * Construct a new EditTextCell that will use a
   * {@link SimpleSafeHtmlRenderer}.
   */
  public SmartEditSelectionCell() {
	 this(SimpleSafeHtmlRenderer.getInstance());
	 
  }
  
  public SmartEditSelectionCell(ValidarCambio validarCambio) {
		this(SimpleSafeHtmlRenderer.getInstance());
		this.validarCambio = validarCambio;
	  } 

  /**
   * Construct a new EditTextCell that will use a given {@link SafeHtmlRenderer}
   * to render the value when not in edit mode.
   * 
   * @param renderer a {@link SafeHtmlRenderer SafeHtmlRenderer<String>}
   *          instance
   */
  public SmartEditSelectionCell(SafeHtmlRenderer<String> renderer) {
    super("click", "keyup", "keydown", "blur", "change");
    if (template == null) {
    		template = GWT.create(Template.class);
    }
    if (renderer == null) {
      throw new IllegalArgumentException("renderer == null");
    }
    this.renderer = renderer;
  }

  @Override
  public boolean isEditing(Context context, Element parent, String value) {
    ViewData viewData = getViewData(context.getKey());
    return viewData == null ? false : viewData.isEditing();
 }

  @Override
  public void onBrowserEvent(Context context, Element parent, String value,
      NativeEvent event, ValueUpdater<String> valueUpdater) {
    Object key = context.getKey();
    ViewData viewData = getViewData(key);

    if (viewData != null && viewData.isEditing()) {
      // Handle the edit event.
      editEvent(context, parent, value, viewData, event, valueUpdater);
    } else {
      String type = event.getType();
      int keyCode = event.getKeyCode();
      boolean enterPressed = "keyup".equals(type)
          && keyCode == KeyCodes.KEY_ENTER;
      if ("click".equals(type) || enterPressed) {
        // Go into edit mode.
        if (viewData == null) {
          viewData = new ViewData(value);
          setViewData(key, viewData);
        } else {
          viewData.setEditing(true);
        }

        if(this.isEditable()){        	
        	edit(context, parent, value);
        }	
      }      
    }
    
  }

  @Override
  public void render(Context context, String value, SafeHtmlBuilder sb) {
    // Get the view data.
    Object key = context.getKey();
    ViewData viewData = getViewData(key);
    if (viewData != null && !viewData.isEditing() && value != null
        && value.equals(viewData.getText())) {
      clearViewData(key);
      viewData = null;
    }
    
    
    if (viewData != null) {
      String text = viewData.getText();
      if (viewData.isEditing()) {
        /*
         * Do not use the renderer in edit mode because the value of a text
         * input element is always treated as text. SafeHtml isn't valid in the
         * context of the value attribute.
         */
    	  int selectedIndex = getSelectedIndex(viewData == null ? value : viewData.getText());
    	    sb.appendHtmlConstant("<select tabindex=\"-1\">");
    	    int index = 0;
    	    for (ObjetoSelectCell option : options) {
    	      if (index++ == selectedIndex) {
    	        sb.append(template.selected(option.getDescripcion(), option.getDescripcion()));
    	      } else {
    	        sb.append(template.deselected(option.getDescripcion(), option.getDescripcion()));
    	      }
    	    }
    	    sb.appendHtmlConstant("</select>");
      } else {
        // The user pressed enter, but view data still exists.
        sb.append(renderer.render(text));
      }
    } else if (value != null) {
      sb.append(renderer.render(value));
    }
  }
  
  private int getSelectedIndex(String value) {
    Integer index = indexForOption.get(value);
    if (index == null) {
      return -1;
    }
    return index.intValue();
  }
  
  @Override
  public boolean resetFocus(Context context, Element parent, String value) {
    if (isEditing(context, parent, value)) {
      getOptionElement(parent).focus();
      return true;
    }
    return false;
  }

  /**
   * Convert the cell to edit mode.
   *
   * @param context the {@link Context} of the cell
   * @param parent the parent element
   * @param value the current value
   */
  protected void edit(Context context, Element parent, String value) {
    setValue(context, parent, value);
    OptionElement input = getOptionElement(parent);
    input.focus();
  }

  /**
   * Convert the cell to non-edit mode.
   * 
   * @param context the context of the cell
   * @param parent the parent Element
   * @param value the value associated with the cell
   */
  private void cancel(Context context, Element parent, String value) {
    clearInput(getOptionElement(parent));
    setValue(context, parent, value);
  }

  /**
   * Clear selected from the input element. Both Firefox and IE fire spurious
   * onblur events after the input is removed from the DOM if selection is not
   * cleared.
   *
   * @param input the input element
   */
  private native void clearInput(Element input) /*-{
    if (input.selectionEnd)
      input.selectionEnd = input.selectionStart;
    else if ($doc.selection)
      $doc.selection.clear();
  }-*/;

  /**
   * Commit the current value.
   * 
   * @param context the context of the cell
   * @param parent the parent Element
   * @param viewData the {@link ViewData} object
   * @param valueUpdater the {@link ValueUpdater}
   */
  private void commit(Context context, Element parent, ViewData viewData,
      ValueUpdater<String> valueUpdater) {
    String value = updateViewData(parent, viewData, false);
    clearInput(getOptionElement(parent));
    setValue(context, parent, viewData.getOriginal());
    if (valueUpdater != null) {
      valueUpdater.update(value);
    }
  }

  private void editEvent(Context context, Element parent, String value,
      ViewData viewData, NativeEvent event, ValueUpdater<String> valueUpdater) {	  
    String type = event.getType();
    boolean keyUp = "keyup".equals(type);
    boolean keyDown = "keydown".equals(type);
    if ("change".equals(type)) {
    	 commit(context, parent, viewData, valueUpdater);
    }
    else 
      if (keyUp || keyDown) 
      {
      int keyCode = event.getKeyCode();
   
      if (keyUp && keyCode == KeyCodes.KEY_ENTER) {
        // Commit the change.
         commit(context, parent, viewData, valueUpdater);
      } else if (keyUp && keyCode == KeyCodes.KEY_ESCAPE) 
      {
        // Cancel edit mode.
        String originalText = viewData.getOriginal();
        if (viewData.isEditingAgain()) {
          viewData.setText(originalText);
          viewData.setEditing(false);
        } 
        else 
        {
          setViewData(context.getKey(), null);
        }
        cancel(context, parent, value);
      } 
      else 
      {
        // Update the text in the view data on each key.
        updateViewData(parent, viewData, true);
      }
     }
      else if (BLUR.equals(type)) {
   	  // Commit the change. Ensure that we are blurring the input element and
   	  // not the parent element itself.
   	  EventTarget eventTarget = event.getEventTarget();
   	  if (Element.is(eventTarget)) {
   	    Element target = Element.as(eventTarget);
   	    if ("select".equals(target.getTagName().toLowerCase())) {
   	      commit(context, parent, viewData, valueUpdater);
   	      
   	    }
   	  }
   	}
   	
       
  }

  /**
   * Get the input element in edit mode.
   */
  private OptionElement getOptionElement(Element parent) {
    return parent.getFirstChild().<OptionElement> cast();
  }

  /**
   * Update the view data based on the current value.
   *
   * @param parent the parent element
   * @param viewData the {@link ViewData} object to update
   * @param isEditing true if in edit mode
   * @return the new value
   */
  private String updateViewData(Element parent, final ViewData viewData,
      boolean isEditing) {
	
	OptionElement option = (OptionElement) parent.getFirstChild();
    String value = option.getValue();  
    ObjetoSelectCell object = this.options.get(indexForOption.get(value));
   
    
    if(validarCambio != null){
    	if(validarCambio.esValido()){
	    	if(!object.getDescripcion().equals(validador)){
	    		 viewData.setText(object.getDescripcion());
	    		 viewData.setEditing(isEditing);
	    		 return object.getCodigo();
	    	}
	    	else{
	    		viewData.setEditing(false);
	    		object = this.options.get(indexForOption.get(viewData.getOriginal()));
				viewData.setText(viewData.getOriginal());
				return object.getCodigo();
	    	}
    	}
    	else{
    		 viewData.setText(object.getDescripcion());
	   		 viewData.setEditing(isEditing);
	   		 return object.getCodigo();
    	}
    }
    else{
    	viewData.setText(object.getDescripcion());
		 viewData.setEditing(isEditing);
		 return object.getCodigo();
    }
            
  }
  
  
  private HashMap<String, Integer> indexForOption = new HashMap<String, Integer>();

  private List<ObjetoSelectCell> options;
  
  public void putOptions(List<ObjetoSelectCell> options){
	  indexForOption.clear();
	  this.options = new ArrayList<ObjetoSelectCell>(options);
	    int index = 0;
	    for (ObjetoSelectCell option : options) {
	      indexForOption.put(option.getDescripcion(), index++);
	    }
  }


	public String getValidador() {
		return validador;
	}
	
	
	public void setValidador(String validador) {
		this.validador = validador;
	}
  
  
}